// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include <pch.h>

#include "ABIModuleLoader.h"
#include "HStringHelper.h"

#include <folly/json.h>

namespace ABI {
namespace react {
namespace uwp {

ABIModule::ABIModule(const Microsoft::WRL::ComPtr<ABI::react::uwp::IModule>& module)
   : m_module(module)
{
  assert(module != nullptr);
}

std::string ABIModule::getName()
{
    HSTRING name;
    m_module->get_Name(&name);

    return HSTRINGToString(name);
}

std::map<std::string, folly::dynamic> ABIModule::getConstants()
{
  using namespace ABI::Windows::Foundation;
  using namespace ABI::Windows::Foundation::Collections;

  Microsoft::WRL::ComPtr<IMapView<HSTRING, HSTRING>> constantsMapView;
  Microsoft::WRL::ComPtr<IIterable<IKeyValuePair<HSTRING, HSTRING>*>> constantsIterable ;
  Microsoft::WRL::ComPtr<IIterator<IKeyValuePair<HSTRING, HSTRING>*>> constantsIter;

  m_module->get_Constants(constantsMapView.GetAddressOf());
  constantsMapView.As(&constantsIterable);
  constantsIterable->First(&constantsIter);

  std::map<std::string, folly::dynamic> ret;
  boolean hasCurrent;

  do
  {
    constantsIter->get_HasCurrent(&hasCurrent);

    if (!hasCurrent)
      break;

    Microsoft::WRL::ComPtr<IKeyValuePair<HSTRING, HSTRING>> pair;
    constantsIter->get_Current(pair.GetAddressOf());

    HSTRING key, value;
    pair->get_Key(&key);
    pair->get_Value(&value);

    ret.emplace(HSTRINGToString(key), folly::parseJson(HSTRINGToString(value)));
    constantsIter->MoveNext(&hasCurrent);
  } while (hasCurrent);

  return ret;
}

std::vector<facebook::xplat::module::CxxModule::Method> ABIModule::getMethods()
{
  using namespace ABI::Windows::Foundation;
  using namespace ABI::Windows::Foundation::Collections;

  std::vector<facebook::xplat::module::CxxModule::Method> ret;
  boolean hasCurrent;

  // Add methods without callback
  Microsoft::WRL::ComPtr<IMapView<HSTRING, ABI::react::uwp::IMethodDelegate*>> methodsMapView;
  Microsoft::WRL::ComPtr<IIterable<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodDelegate*>*>> methodsIterable ;
  Microsoft::WRL::ComPtr<IIterator<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodDelegate*>*>> methodsIter;

  m_module->get_Methods(&methodsMapView);
  methodsMapView.As(&methodsIterable);
  methodsIterable->First(&methodsIter);

  do
  {
    methodsIter->get_HasCurrent(&hasCurrent);

    if (!hasCurrent)
      break;

    Microsoft::WRL::ComPtr<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodDelegate*>> pair;
    methodsIter->get_Current(pair.GetAddressOf());

    HSTRING key;
    ABI::react::uwp::IMethodDelegate* value;
    pair->get_Key(&key);
    pair->get_Value(&value);

    ret.push_back(facebook::xplat::module::CxxModule::Method(HSTRINGToString(key), [this, value](folly::dynamic args)
    {
      std::string str = folly::toJson(args);

      HSTRING para = StringToHSTRING(str);
      value->Invoke(para);
      WindowsDeleteString(para);
    }));

    methodsIter->MoveNext(&hasCurrent);

  } while ( hasCurrent );

    // Add methods with callback
  Microsoft::WRL::ComPtr<IMapView<HSTRING, ABI::react::uwp::IMethodWithCallbackDelegate*>> methodsWithCallBackMapView;
  Microsoft::WRL::ComPtr<IIterable<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodWithCallbackDelegate*>*>> methodsWithCallBackIterable ;
  Microsoft::WRL::ComPtr<IIterator<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodWithCallbackDelegate*>*>> methodsWithCallBackIter;

  m_module->get_MethodsWithCallback(&methodsWithCallBackMapView);
  methodsWithCallBackMapView.As(&methodsWithCallBackIterable);
  methodsWithCallBackIterable->First(&methodsWithCallBackIter);

  do
  {
    boolean hasCurrent;
    methodsWithCallBackIter->get_HasCurrent(&hasCurrent);

    if (!hasCurrent)
      break;

    Microsoft::WRL::ComPtr<IKeyValuePair<HSTRING, ABI::react::uwp::IMethodWithCallbackDelegate*>> pair;
    methodsWithCallBackIter->get_Current(pair.GetAddressOf());

    HSTRING key;
    ABI::react::uwp::IMethodWithCallbackDelegate* value;
    pair->get_Key(&key);
    pair->get_Value(&value);

    ret.push_back(facebook::xplat::module::CxxModule::Method(HSTRINGToString(key), [this, value](folly::dynamic args, Callback cb)
    {
      std::string str = folly::toJson(args);

      HSTRING para = StringToHSTRING(str);

      auto callback = Microsoft::WRL::Callback<ABI::react::uwp::IMethodCallback>(
        [cb](IVectorView<HSTRING>* argVectorView)
      {
        std::vector<folly::dynamic> args;

        if (argVectorView != nullptr)
        {
          unsigned int size = 0;
          argVectorView->get_Size(&size);

          for (unsigned int i = 0; i < size; i++)
          {
            HSTRING itemString;
            argVectorView->GetAt(i, &itemString);
            if (itemString == NULL)
              args.push_back(nullptr);
            else
              args.push_back(folly::parseJson(HSTRINGToString(itemString)));
          }
        }

        cb(args);
        return S_OK;
      });

      value->Invoke(para, callback.Get());
      WindowsDeleteString(para);
    }));

    methodsWithCallBackIter->MoveNext(&hasCurrent);
  } while (true);

  return ret;
}

ABIModuleLoader::ABIModuleLoader()
{
}

void ABIModuleLoader::RegisterModule(Microsoft::WRL::ComPtr<ABI::react::uwp::IModule>& module)
{
  m_modules.push_back(module);
}

std::vector<facebook::react::NativeModuleDescription> ABIModuleLoader::GetModules(const std::shared_ptr<facebook::react::MessageQueueThread>& defaultQueueThread)
{
  std::shared_ptr<facebook::react::MessageQueueThread> queueThread(defaultQueueThread);
  std::vector<facebook::react::NativeModuleDescription> modules;
  modules.reserve(m_modules.size());

  for (auto& module : m_modules)
  {
    HSTRING name;
    module->get_Name(&name);

    modules.emplace_back(
      HSTRINGToString(name),
      [module, queueThread]()
      {
        return std::make_unique<ABIModule>(module);
      },
      queueThread
      );
  }

  return modules;
}

}
}
}
